Passa al contenuto principale

Sintassi per reti combinatorie

Una rete combinatoria si esprime come un module composto solo da wire, espressioni combinatorie e componenti che sono a loro volta reti combinatorie.

module

Il blocco module ... endmodule definisce un tipo di componente, che può poi essere instanziato in altri componenti. La dichiarazione di un module include il suo nome e la lista delle sue porte.

module nome_rete ( porta1, porta2, ... );
...
endmodule

input e output

Per ciascuna porta di un module, dichiariamo se è di input o output, e di quanti bit è composta. Se non specificata, la dimensione default è 1. La dichiarazione di porte con le stesse caratteristiche si può fare nella stessa riga.

Le porte input sono dei wire il cui valore va assegnato al di fuori di questa rete.

Le porte output sono dei wire il cui valore va assegnato all'interno di questa rete.

module nome_rete ( porta1, porta2, porta3, porta4 );
input [3:0] porta1, porta2;
output [3:0] porta3;
output porta4;
...
endmodule
inout

Non usiamo porte inout nelle reti combinatorie.

wire

Un wire è un filo che trasporta un valore logico. Se non specificata, la dimensione default è 1. La dichiarazione di wire con le stesse caratteristiche si può fare nella stessa riga.

    wire [3:0] w1, w2;
wire w3, w4, w5;

Con uno statement assign possiamo associare al wire una espressione combinatoria: il wire assumerà continuamente il valore dell'espressione, rispondendo ai cambiamenti dei suoi operandi. Lo statement assign può includere un fattore di ritardo, #T, per indicare che il valore del filo segue il valore dell'espressione con ritardo di T unità.

    assign #1 w5 = w3 & w4; 

Un wire può essere associato a una porta di un module, come mostrato nella sezione successiva.

Usare un module in un altro module

Una volta definito un module, possiamo instanziare componenti di questo tipo in un altro module.

    nome_module nome_istanza (
.porta1(...), .porta2(...), ...
);

All'interno degli statement .porta(...) specichiamo quale porta, espressione o wire del module corrente va collegato alla porta del module instanziato.

Insieme agli statement assign e l'uso di wire, questo ci permette di comporre reti combinatorie su diversi livelli di complessità e con poca duplicazione del codice.

Come esempio, costruiamo un and a 1 ingresso e lo usiamo per comporre un and a 3 ingressi.

module and(a, b, z);
input a, b;
output z;

assign #1 z = a & b;
endmodule

module and2(a, b, c, z);
input a, b, c;
output z;

wire z1;
and a1(
.a(a), .b(b),
.z(z1)
);

and a2(
.a(c), .b(z1),
.z(z)
);
endmodule

Tabelle di verità

Talvolta il modo più immediato per esprimere una rete combinatoria è tramite la sua tabella di verità. È anche noto che data una tabella di verità possiamo ottenere una sintesi della rete combinatoria, utilizzando metodi come le mappe di Karnaugh.

In Verilog, il modo più immediato di esprimere una tabella di verità è utilizzando una catena di operatori ternari.

module and (x, y, z);
input x, y;
output z;
assign #1 z =
({x,y} == 2'b00) ? 1'b0 :
({x,y} == 2'b00) ? 1'b0 :
({x,y} == 2'b00) ? 1'b0 :
/*{x,y} == 2'b11*/ 1'b1;

Un'alternativa è l'uso di function e casex.

module and (x, y, z);
input x, y;
output z;
assign #1 z = tabella_verita({a, b});

function tabella_verita;
input [1:0] ab;
casex(ab)
2'b00: tabella_verita = 1'b0;
2'b01: tabella_verita = 1'b0;
2'b10: tabella_verita = 1'b0;
2'b11: tabella_verita = 1'b1;
endcase
endfunction
endmodule

Per indicare tabelle di verità con più di un bit in uscita si scrive, per esempio, function [1:0] tabella_verita;. Nel casex si può utilizzare anche un caso default, scrivendo come ultimo caso default: tabella_verita = ...;.

Attenzione all'uso delle function

Le function sono blocchi di codice da eseguire, parti del behavioral modelling di Verilog. Il simulatore ne svolge i passaggi come un programma, senza consumare tempo e senza alcun corrispettivo hardware previsto. È per questo, per esempio, che dobbiamo specificare noi il tempo consumato nello statement assign.

L'uso mostrato qui delle function è l'unico ammesso per una sintesi di reti combinatorie. In presenza di ogni altra elaborazione algoritmica, di cui non sia evidente il corrispettivo hardware, sarà invece considerata una descrizione di rete combinatoria.

Multiplexer

I multiplexer sono da considerarsi noti e sintetizzabili, e si possono esprimere con uno o più operatori ternari ?.

Operatore ternario

La sintassi è della forma cond ? v_t : v_f, dove cond è un predicato (espressione true o false) mentre v_t e v_f sono espressioni dello stesso tipo.

L'espressione ha valore v_t se il predicato cond è true, v_f altrimenti.

Per un multiplexer con selettore a 1 bit, basterà un solo ?.

input sel;
assign #1 multiplexer = sel ? x0 : x1;

Per un selettore a più bit si dovranno usare in serie per gestire più casi

input [1:0] sel;
assign #1 multiplexer =
(sel == 2'b00) ? x0 :
(sel == 2'b01) ? x1 :
(sel == 2'b10) ? x2 :
/*sel == 2'b11*/ x3 :

Reti parametrizzate

In un module si possono definire parametri per generalizzare la rete. In particolare, questo è frequentemente utilizzato in reti_standard.v per fornire reti il cui dimensionamento è da specificare.

Per esempio, vediamo come è definita una rete di somma a N bit.

module add( 
x, y, c_in,
s, c_out, ow
);
parameter N = 2;

input [N-1:0] x, y;
input c_in;

output [N-1:0] s;
output c_out, ow;

assign #1 {c_out, s} = x + y + c_in;
assign #1 ow = (x[N-1] == y[N-1]) && (x[N-1] != s[N-1]);
endmodule

Con N = 2 viene impostato il valore di default del parametro. Quando instanziamo la rete altrove, possiamo modificare questo parametro, per esempio per ottenere un sommatore a 8 bit.

    add #( .N(8) ) a (
...
);

Un module può avere più di un parametro, che possono essere impostati indipendentemente.

    nome_modulo #( .nome_parametro1(v1), .nome_parametro2(v2)... ) nome_istanza (
...
);
Immutabilità dei parametri

I parametri determinano la quantità di hardware, che non è mutabile! I valori associati devono essere costanti.

Parametrizzazione e sintesi di reti combinatorie

La parametrizzazione è facilmente applicabile a descrizioni di reti combinatorie dove si usano espressioni combinatorie che il simulatore è facilmente in grado di adattare a diverse quantità di bit.

È molto più complicato applicarla a sintesi di reti combinatorie, dato che non si possono instanziare componenti in modo parametrico, per esempio N full adder da 1 bit per sintetizzare un full adder a N bit.